"""
This is a part of rdf-pypath <http://code.google.com/p/rdf-pypath>.

This is a proof of concept of an RDF-path language integrated with Python.

Starting with a 'Root' (a node rooted in a graph), one can explore the graph
by simply writing a path composed of URIs:

  me = Root(URIRef("http://champin.net/foaf.rdf#pa"), a_graph)
  names = me/FOAF.knows/FOAF.name  # the names of the persons I know

Inverse properties can be used by prefixing a URI with '-':

  similar = me/RDF.type/-RDF.type  # everything that has the same type as I

The bracket-notation, alla XPath, can be used to filter a result with a
condition (a function returning a boolean):

  some_friends = me/foaf:knows[cond]  # those who satisfy cond

A cunning operator overloading provides a nice syntax:

  johns = me/foaf:knows[X/foaf:name == "John"]  # all my friends named John
  
Enjoy!

(c) 2010 Pierre-Antoine Champin / Université de Lyon
"""

from rdflib import Literal, URIRef

FOLLOW = 1
FILTER = 2

class Root(object):
    "An RDF node rooted in a graph"

    def __init__(self, node, graph):
        self.node = node
        self.graph = graph

    def __new__(cls, node, graph):
        "If node is arleady a Root, graph is ignore and node is returned"
        if isinstance(node, Root):
            return node
        else:
            return object.__new__(cls)


    def __hash__(self):
        return hash(self.node) | hash(self.graph)

    def __eq__(self, other):
        return isinstance(other, Root) \
                   and self.node == other.node \
                   and self.graph == other.graph

    def __div__(self, other):
        "A root can be used to start a path"
        return RootSet([self])/other

    def __getitem__(self, other):
        "A root can be used to start a path (even with a condition)"
        return RootSet([self])[other]


class RootSet(frozenset):
    "A set of Root, as returned by a path"
    def __div__(self, path):
        # this is where the real work happens
        if not isinstance(path, Path):
            path = X/path
        r = RootSet(self)
        for what, how in path:
            if what is FOLLOW:
                if isinstance(how, URIRef):
                    r = RootSet( Root(obj, root.graph)
                                 for root in r
                                 for obj in root.graph.objects(root.node, how)
                                 )
                else:
                    r = RootSet( Root(obj, root.graph)
                                 for root in r
                                 for obj in how(root)
                                 )
            elif what is FILTER:
                if isinstance(how, Path):
                    r = RootSet( root for root in r if root/how )
                else:
                    r = RootSet( root for root in r if how(root) )
        return r
                

    def __getitem__(self, test):
        return self/(X[test])

    @property
    def nodes(self):
        return frozenset( root.node for root in self )


class Path(tuple):
    def __new__(cls, elements=()):
        return tuple.__new__(cls, elements)

    def __div__(self, other):
        if isinstance(other, Path):
            return Path(self+other)
        else:
            return Path(self+((FOLLOW, other),))

    def __getitem__(self, other):
        if isinstance(other, URIRef):
            other = X/other
        return Path(self+((FILTER, other),))

    def __eq__(self, other):
        truth = super(Path, self).__eq__(other)
        if isinstance(other, Path):
            def function(root):
                return root/self == root/other
        else:
            def function(root):
                return [ i.node for i in root/self ] == [other]
        return bool_function(function, truth)

X = Path() # this allows to force a cast to Path like this: X/uri


class bool_function(object):
    """Bundle a function with a truth value"""
    def __init__(self, function, truth):
        self.function = function
        self.truth = truth

    def __call__(self, *args, **kw):
        return self.function(*args, **kw)

    def __nonzero__(self):
        return bool(self.truth)

# monkey patch URIRef

def uriref_div(self, other):
    return X/self/other
URIRef.__div__ = uriref_div

def uriref_getitem(self, index):
    if isinstance(index, int):
        return super(URIRef, self).__getitem__(index)
    else:
        return (X/self)[index]
URIRef.__getitem__ = uriref_getitem

def uriref_neg(self):
    def function(root):
        return root.graph.subjects(self, root.node)
    return function
URIRef.__neg__ = uriref_neg

# NB: monkey patching URIRef.__eq__ is tricky,
# because rdflib relies of testing equality of URIRefs...
# So for the moment, instead of
#   [FOAF.name == "a name"]
# one has to write
#   [X/FOAF.name == "a name"]
# Note that the / operator builds a path, so one can still write
#   [FOAF.knows/FOAF.name == "a name"]

#def uriref_eq(self, other):
#    return Path([self]) == other
#URIRef.__eq__ = uriref_eq



if __name__ == "__main__":
    from pprint import pprint
    from rdflib import Graph, Namespace, RDF
    from rdflib.Graph import ConjunctiveGraph as Graph # remove for rdflib 3
    g = Graph()
    g.load("http://champin.net/foaf.rdf")
    me = Root(URIRef("http://champin.net/foaf.rdf#pa"), g)
    FOAF = Namespace("http://xmlns.com/foaf/0.1/")

    def rootset_str(self):
        r = u""
        for root in self:
            r = u"%s%s\n" % (r, root.node.n3())
        return r.encode("utf8")
    RootSet.__str__ = rootset_str

    print "   -- my types"
    print me/RDF.type
    print "   -- people I know"
    print me/FOAF.knows
    print "   -- people I know who have a name"
    print me/FOAF.knows[FOAF.name]
    assert me/FOAF.knows[FOAF.name] == me/(FOAF.knows[FOAF.name])
    print "   -- names of the people I know"
    print me/FOAF.knows/FOAF.name
    assert me/FOAF.knows/FOAF.name ==  me/(FOAF.knows/FOAF.name)
    print "   -- people I know whose name is 'Olivier Aubert'"
    print me/FOAF.knows[X/FOAF.name == Literal("Olivier Aubert")]
    print "   -- people who know the people I know"
    print me/FOAF.knows/-FOAF.knows
